/*
 * Copyright (c) 2021 Talkweb Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ntpUdp.h"
#include "hi_wifi_api.h"
#include "lwip/ip_addr.h"
#include "lwip/netifapi.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "lwip/inet.h"
#include "lwip/opt.h"
#include "lwip/sntp.h"

#define THREAD_UDP_STACK_SIZE 4096
#define TIME_OFFSET 8 * 3600
#define SEVENZYYEARS 2208988800UL

#define UDP_PACKET_SIZE 48
#define IP_BUFFER_SIZE 16

volatile int64_t currentEpochTime = 0;
uint32_t ntpSocket = -1;
fd_set read_set;

uint8_t sendBuf[READ_RECV_BUFFER_SIZE] = {0};
uint8_t recvBuf[READ_RECV_BUFFER_SIZE] = {0};
uint8_t ipAddress[16] = {0};
uint8_t timeString[48] = {0};
volatile BOOL udpThreadActive = FALSE;

BOOL GetThreadRunStatus()
{
    return udpThreadActive;
}

void SetThreadRunStatus(BOOL status)
{
    udpThreadActive = status;
}

int64_t GetEpochTime()
{
    return currentEpochTime;
}

char *GetEpochTimeString(TIME_FORMAT format, const char *hostname, uint16_t port)
{
    currentEpochTime = 0;
    if (StartUdpConnect(hostname, port))
    {
        if (format == HOUR_MIN_SEC)
        {
            memset(timeString, 0, 48);
            long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
            int hours = (rawTime % 86400L) / 3600;
            int minutes = (rawTime % 3600) / 60;
            int seconds = rawTime % 60;
            if (sprintf_s(timeString, 48, "%02d:%02d:%02d", hours, minutes, seconds) == -1)
            {
                printf("sprintf time error\n");
                return NULL;
            }
        }
        else if (format == YEAR_MONTH_DAY)
        {
            memset(timeString, 0, 48);
            long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
            struct tm *time_tm = NULL;
            time_tm = gmtime(&rawTime);
            if (sprintf_s(timeString, 48, "%04d/%02d/%02d", time_tm->tm_year + 1900, time_tm->tm_mon + 1, time_tm->tm_mday) == -1)
            {
                printf("sprintf time error\n");
                return NULL;
            }
        }
        else if (format == YEAR_MONTH_DAY_HOUR_MIN_SEC)
        {
            memset(timeString, 0, 48);
            long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
            struct tm *time_tm = NULL;
            time_tm = gmtime(&rawTime);
            if (sprintf_s(timeString, 48, "%04d/%02d/%02d %02d:%02d:%02d", time_tm->tm_year + 1900, time_tm->tm_mon + 1, time_tm->tm_mday,
                          time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec) == -1)
            {
                printf("sprintf time error\n");
                return NULL;
            }
        }
        else
        {
            // do nothing
            return NULL;
        }
        return timeString;
    }

    return NULL;
}

BOOL GetTimeTm(const char *hostname, const uint16_t port, struct tm *time_tm)

{
    if (hostname == NULL || time_tm == NULL)
    {
        printf("time tm is NULL\n");
        return FALSE;
    }

    if (StartUdpConnect(hostname, port))
    {
        memset(timeString, 0, 48);
        long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
        struct tm *time_temp = NULL;
        time_temp = gmtime(&rawTime);
        time_tm->tm_sec = time_temp->tm_sec;
        time_tm->tm_min = time_temp->tm_min;
        time_tm->tm_hour = time_temp->tm_hour;
        time_tm->tm_mday = time_temp->tm_mday;
        time_tm->tm_mon = time_temp->tm_mon;
        time_tm->tm_year = time_temp->tm_year;
        time_tm->tm_wday = time_temp->tm_wday;
        time_tm->tm_yday = time_temp->tm_yday;
        time_tm->tm_isdst = time_temp->tm_isdst;
        printf("GetTimeTm:%04d/%02d/%02d %02d:%02d:%02d\n", time_tm->tm_year + 1900, time_tm->tm_mon + 1, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
        return TRUE;
    }

    return FALSE;
}

void getFormattedTime(char *timeFormat, unsigned long currentEpoc)
{
    if (timeFormat == NULL)
    {
        printf("container buffer is null\n");
        return;
    }

    memset(timeFormat, 0, 48);
    long rawTime = currentEpoc + TIME_OFFSET; //getEpochTime();
    int hours = (rawTime % 86400L) / 3600;
    int minutes = (rawTime % 3600) / 60;
    int seconds = rawTime % 60;

    if (sprintf_s(timeFormat, 48, "%02d:%02d:%02d", hours, minutes, seconds) == -1)
    {
        printf("sprintf time error\n");
    }
}

static void setSendBuf(void)
{
    memset(sendBuf, 0, READ_RECV_BUFFER_SIZE);
    //sendBuf[0] = 0b11100011;   // LI, Version, Mode
    sendBuf[0] = 0xE3;
    sendBuf[1] = 0;    // Stratum, or type of clock
    sendBuf[2] = 6;    // Polling Interval
    sendBuf[3] = 0xEC; // Peer Clock Precision
    // 8 bytes of zero for Root Delay & Root Dispersion
    sendBuf[12] = 49;
    sendBuf[13] = 0x4E;
    sendBuf[14] = 49;
    sendBuf[15] = 52;
}

uint32_t GetIpByHostName(const char *hostname, uint8_t *ipAddr)
{
    (void)ipAddr;
    if (!hostname || !ipAddr)
    {
        return -1;
    }

    struct hostent *host = gethostbyname(hostname);
    if (!host)
    {
        return -1;
    }
    printf("HostName :%s\n", host->h_name);
    // 获取所有的地址

    if (strncpy_s(ipAddr, IP_BUFFER_SIZE,
                  inet_ntoa((*(struct in_addr *)(host->h_addr_list[0]))),
                  IP_BUFFER_SIZE) != 0)
    {
        printf("copy str error\n");
        return -1;
    }
    else
    {
        printf("get ip address is %s\n", ipAddr);
    }

    return 0;
}

BOOL StartUdpConnect(const char *hostname, uint16_t port)
{
    int ret;
    int nZero = 0;
    struct sockaddr_in clientaddr;
    ntpSocket = socket(PF_INET, SOCK_DGRAM, 0);

    if (ntpSocket == -1)
    {
        printf("create socket fd failed\n");
    }
    else
    {
        printf("create socket fd success\n");
    }

    uint32_t flag = fcntl(ntpSocket, F_GETFL, 0);
    ret = fcntl(ntpSocket, F_SETFL, flag | O_NONBLOCK);
    if (ret == -1)
    {
        printf("set nonblock failed\n");
        if (ntpSocket >= 0)
        {
            close(ntpSocket);
        }
        return FALSE;
    }

    setsockopt(ntpSocket, SOL_SOCKET, SO_RCVBUF, (char *)&nZero, sizeof(int));
    if (GetIpByHostName(hostname, ipAddress) != 0)
    {
        printf("dns failed\n");
        if (ntpSocket >= 0)
        {
            close(ntpSocket);
        }
        return FALSE;
    }

    int reuse = 1;
    if (setsockopt(ntpSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        printf("setsockopt failed\n");
        if (ntpSocket >= 0)
        {
            close(ntpSocket);
        }
        return FALSE;
    }

    //服务器 ip port
    bzero(&clientaddr, sizeof(clientaddr));
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = inet_addr(ipAddress);
    clientaddr.sin_port = htons(port);
    setSendBuf();
    if (sendto(ntpSocket, sendBuf, UDP_PACKET_SIZE, 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr)) == UDP_PACKET_SIZE)
    {
    }
    else
    {
        printf("send ntp socket failed\n");
        if (ntpSocket >= 0)
        {
            close(ntpSocket);
        }
        return FALSE;
    }

    uint8_t counts = 0;
    while (counts < 100)
    {
        struct sockaddr_in serverAddr = {0};
        int sizeClientAddr = sizeof(struct sockaddr_in);
        memset(recvBuf, 0, sizeof(recvBuf));
        FD_ZERO(&read_set);
        FD_CLR(ntpSocket, &read_set);
        FD_SET(ntpSocket, &read_set);
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 10000; // 10ms
        ret = select(ntpSocket + 1, &read_set, NULL, NULL, &tv);
        if (ret > 0)
        {
            if (FD_ISSET(ntpSocket, &read_set))
            {
                memset(recvBuf, 0, 50);
                ret = recvfrom(ntpSocket, recvBuf, READ_RECV_BUFFER_SIZE, 0, (struct sockaddr *)&serverAddr, (socklen_t *)&sizeClientAddr);
                if (ret > 0)
                {
                    //short highword = (recvBuf[40]<<8) + recvBuf[41];
                    //short lowword = (recvBuf[42]<<8) + recvBuf[43];
                    //currentEpochTime = (highword << 16) + lowword;
                    //currentEpochTime -=  SEVENZYYEARS;
                    currentEpochTime = (unsigned long)recvBuf[40] << 24;
                    currentEpochTime |= (unsigned long)recvBuf[41] << 16;
                    currentEpochTime |= (unsigned long)recvBuf[42] << 8;
                    currentEpochTime |= (unsigned long)recvBuf[43];
                    currentEpochTime -= SEVENZYYEARS;
                    if (ntpSocket >= 0)
                    {
                        close(ntpSocket);
                    }
                    return TRUE;
                }
            }
        }
        else
        {
            counts++;
        }
    }
    printf("select failed\n");
    if (ntpSocket >= 0)
    {
        close(ntpSocket);
    }
    return FALSE;
}
